use ordered_float::NotNan;
use super::ast::{Expr,Number,Opt};

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum OpCode {
    PushInt(i64),
    PushFloat(NotNan<f64>),
    Add,
    Sub,
    Mul,
    Div
}

pub fn build_ast(ast:Expr,op_codes:&mut Vec<OpCode>) {
    match ast {
        Expr::Num(Number::Int(int)) => {
            op_codes.push(OpCode::PushInt(int));
        },
        Expr::Num(Number::Float(float)) => {
            op_codes.push(OpCode::PushFloat(float));
        },
        Expr::Opt(l,op,r) => {
            build_ast(*l,op_codes);
            build_ast(*r,op_codes);
            match op {
                Opt::Add =>  op_codes.push(OpCode::Add),
                Opt::Sub =>  op_codes.push(OpCode::Sub),
                Opt::Mul =>  op_codes.push(OpCode::Mul),
                Opt::Div =>  op_codes.push(OpCode::Div),
            };
        },
    };
}

pub struct Context {
    codes: Vec<OpCode>,
    stacks:Vec<Number>
}

impl Context {
    pub fn new() -> Self {
        Context {
            codes: vec![],
            stacks:vec![],
        }
    }

    pub fn set_codes(&mut self,codes:Vec<OpCode>) {
        self.codes = codes;
    }

    pub fn exec(&mut self) -> Option<Number> {
        for opt in self.codes.iter() {
            match opt {
                OpCode::PushInt(int) => {
                    self.stacks.push(Number::Int(*int));
                },
                OpCode::PushFloat(float) => {
                    self.stacks.push(Number::Float(*float));
                },
                OpCode::Add => {
                   let v1 = self.stacks.pop().unwrap().case_int();
                   let v2 = self.stacks.pop().unwrap().case_int();
                   let ret = v1 + v2;
                   self.stacks.push(Number::Int(ret));
                },
                OpCode::Sub => {
                   let v1 = self.stacks.pop().unwrap().case_int();
                   let v2 = self.stacks.pop().unwrap().case_int();
                   let ret = v2 - v1;
                   self.stacks.push(Number::Int(ret));
                },
                OpCode::Mul => {
                   let v1 = self.stacks.pop().unwrap().case_int();
                   let v2 = self.stacks.pop().unwrap().case_int();
                   let ret = v1 * v2;
                   self.stacks.push(Number::Int(ret));
                },
                OpCode::Div => {
                   let v1 = self.stacks.pop().unwrap().case_int();
                   let v2 = self.stacks.pop().unwrap().case_int();
                   let ret = v2 / v1;
                   self.stacks.push(Number::Int(ret));
                },
            }
        }

       self.stacks.pop()
    }
}